package com.abo.yaas.view;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import com.vaadin.Application;
import com.vaadin.service.ApplicationContext.TransactionListener;

@SuppressWarnings("serial")
public class ViewHandler implements TransactionListener {
    /**
     * Activate the view with the given viewId. You can specify any given amount
     * of parameters for the activation request. Each parameter is forwarded to
     * the View's activated() method.
     * 
     * @param viewId
     *            The view's viewId
     * @param changeUriFramgent
     *            Should the uri fragment be changed if the view has one set
     * @param params
     *            Parameters used for activating the view
     */
    public static void activateView(Object viewId, boolean changeUriFramgent,
            Object... params) {
        if (viewId == null || !instance.get().viewMap.containsKey(viewId)
                || !instance.get().parentMap.containsKey(viewId)) {
            return;
        }

        final ViewItem item = instance.get().viewMap.get(viewId);
        final ViewContainer parent = instance.get().parentMap.get(viewId);

        final DispatchEvent event = new DispatchEvent(item, params);
        try {
            for (final DispatchEventListener listener : instance.get().listeners) {
                listener.preActivation(event);
            }
        } catch (final DispatchException e) {
            return;
        }

        parent.activate(item.getView());

        item.getView().activated(params);

        // View has been dispatched, send event
        for (final DispatchEventListener listener : instance.get().listeners) {
            listener.postActivation(event);
        }
    }

    /**
     * Activate the view with the given viewId. You can specify any given amount
     * of parameters for the activation request. Each parameter is forwarded to
     * the View's activated() method.
     * 
     * @param viewId
     *            The view's viewId
     * @param params
     *            Parameters used for activating the view
     */
    public static void activateView(Object viewId, Object... params) {
        activateView(viewId, false, params);
    }

    public static void addListener(DispatchEventListener listener) {
        if (listener != null) {
            instance.get().listeners.add(listener);
        }
    }

    /**
     * Add a new view. Returns the viewId. Make sure to set either the view
     * instance or the view class for the ViewItem.
     * 
     * @return The viewId
     */
    public static Object addView() {
        final Object viewId = UUID.randomUUID();
        final ViewItem item = new ViewItem(viewId);
        instance.get().viewMap.put(viewId, item);
        return viewId;
    }

    /**
     * Add a new View to the ViewHandler. Takes as input a viewId. The user is
     * responsible of setting the view class. If the viewId is already in use,
     * then null is returned.
     * 
     * @param viewId
     *            The view's id
     * @return The resulting ViewItem object
     */
    public static ViewItem addView(Object viewId) {
        if (viewId == null) {
            throw new IllegalArgumentException("View id may not be null");
        }

        // Check if the viewId is already in use. If it is, then return null.
        if (instance.get().viewMap.containsKey(viewId)) {
            return null;
        }

        // Create a new ViewItem and add it to the map.
        final ViewItem item = new ViewItem(viewId);
        instance.get().viewMap.put(viewId, item);

        // Check if we have a default ViewFactory defined. If one is defined,
        // then set it to the item.
        if (instance.get().defaultViewFactory != null) {
            item.setFactory(instance.get().defaultViewFactory);
        }
        return item;
    }

    /**
     * Add a new view to the view handler.
     * 
     * @param viewId
     *            The view's id
     * @param parent
     *            Parent view for the given view
     * @return The resulting ViewItem object
     */
    public static ViewItem addView(Object viewId, ViewContainer parent) {
        final ViewItem item = addView(viewId);
        setParent(viewId, parent);
        return item;
    }

    /**
     * Deactivate the view with the given viewId. You can specify any given
     * amount of parameters for the deactivation request. Each parameter is
     * forwarded to the View's deactivated() method.
     * 
     * @param viewId
     *            The view's viewId
     * @param params
     *            Parameters used for activating the view
     */
    public static void deactivateView(Object viewId, Object... params) {
        if (viewId == null || !instance.get().viewMap.containsKey(viewId)
                || !instance.get().parentMap.containsKey(viewId)) {
            return;
        }

        // Get the ViewItem and parent for this viewId
        final ViewItem item = instance.get().viewMap.get(viewId);
        final ViewContainer parent = instance.get().parentMap.get(viewId);

        final DispatchEvent event = new DispatchEvent(item, params);
        try {
            for (final DispatchEventListener listener : instance.get().listeners) {
                listener.preDeactivation(event);
            }
        } catch (final DispatchException e) {
            return;
        }

        parent.deactivate(item.getView());
        item.getView().deactivated(params);

        for (final DispatchEventListener listener : instance.get().listeners) {
            listener.postDeactivation(event);
        }
    }

    /**
     * Get the current default ViewFactory.
     * 
     * @return Default ViewFactory
     */
    public static ViewFactory getDefaultViewFactory() {
        return instance.get().defaultViewFactory;
    }

    /**
     * Fetch the ViewItem for the given viewId. If the viewId is not found, then
     * null is returned.
     * 
     * @param viewId
     * @return The ViewItem object for the given viewId
     */
    public static ViewItem getViewItem(Object viewId) {
        // Check if the viewId exists in the map
        if (viewId != null && instance.get().viewMap.containsKey(viewId)) {
            return instance.get().viewMap.get(viewId);
        }

        return null;
    }

    /**
     * Initializes the {@link ViewHandler} for the given {@link Application}
     * 
     * @param application
     */
    public static void initialize(Application application) {
        if (application == null) {
            throw new IllegalArgumentException("Application may not be null");
        }
        final ViewHandler handler = new ViewHandler(application);
        application.getContext().addTransactionListener(handler);
    }

    public static void removeListener(DispatchEventListener listener) {
        if (listener != null) {
            instance.get().listeners.remove(listener);
        }
    }

    /**
     * Removes the ViewItem from the handler for the given viewId.
     * 
     * @param viewId
     * @return Returns true if the viewId existed, otherwise false.
     */
    public static boolean removeView(Object viewId) {
        if (viewId != null && instance.get().viewMap.containsKey(viewId)) {
            instance.get().viewMap.remove(viewId);
            return true;
        }
        return false;
    }

    /**
     * Set the default ViewFactory which is to be used in all <b>newly</b> added
     * views.
     * 
     * @param defaultViewFactory
     *            Default ViewFactory to be used in all new views.
     */
    public static void setDefaultViewFactory(ViewFactory defaultViewFactory) {
        instance.get().defaultViewFactory = defaultViewFactory;
    }

    /**
     * Set the parent view for the given viewId.
     * 
     * @param viewId
     *            The viewId of the ViewItem
     * @param parent
     *            New parent for the view
     */
    public static void setParent(Object viewId, ViewContainer parent) {
        if (viewId != null && parent != null
                && instance.get().viewMap.containsKey(viewId)) {
            instance.get().parentMap.put(viewId, parent);
        }
    }

    // A map between the view id and the view item
    private final Map<Object, ViewItem> viewMap = new HashMap<Object, ViewItem>();

    // A map between parent ids and parent views
    private final Map<Object, ViewContainer> parentMap = new HashMap<Object, ViewContainer>();

    // A list of all known dispatch event listeners.
    private final List<DispatchEventListener> listeners = new ArrayList<DispatchEventListener>();

    // Store this instance of the view handler in this thread local variable
    private static final ThreadLocal<ViewHandler> instance = new ThreadLocal<ViewHandler>();

    private final Application application;

    private ViewFactory defaultViewFactory = null;

    /**
     * 
     * @param application
     *            Current application instance
     */
    public ViewHandler(Application application) {
        instance.set(this);
        this.application = application;
    }

    @Override
    public void transactionEnd(Application application, Object transactionData) {
        if (this.application == application) {
            instance.set(null);
        }
    }

    @Override
    public void transactionStart(Application application, Object transactionData) {
        // Set the thread local instance
        if (this.application == application) {
            instance.set(this);
        }
    }
}
